﻿namespace StockSharp.AlfaDirect
{
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Globalization;
	using System.Linq;
	using System.Threading;
    using System.Reflection;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.ComponentModel;

	using StockSharp.BusinessEntities;
	using StockSharp.Algo;
	using StockSharp.Algo.Candles;
	using StockSharp.Algo.Logging;
    using StockSharp.AlfaDirect.Tables;
     
    internal struct CandleTimeInfo
    {
        internal DateTime TimeStarted;
        internal bool IsStartedSent;
    };

	/// <summary>
	/// Реализация интерфейса <see cref="ITrader"/>, предоставляющая шлюз взаимодействия с системой AlfaDirect.
	/// </summary>
	public sealed class AlfaTrader : BaseTrader
	{
	    private AlfaWrapper _wrapper;
	    private readonly object _wrapperCreationMutex = new object();
		private Timer _candlesTimer;
		private DateTime _creationTime;
		private readonly Stopwatch _elapsedTime = new Stopwatch();
        private readonly SynchronizedDictionary<CandleToken, CandleTimeInfo> _candleTokens = new SynchronizedDictionary<CandleToken, CandleTimeInfo>();
	    /// <summary>
        /// Ордера которые ушли на регистрацию но еще не пришли от терминала.
        /// Все это нужно, чтобы выявлять ордера пришедшие с пустым полем комментария, в котором находится номер транзакции.
	    /// </summary>
        private readonly SynchronizedSet<Order> _pendingOrders = new SynchronizedSet<Order>();  

        /// <summary>
        /// Получить текущее состояние подключния терминала.
        /// </summary>
        public override bool IsConnected
        {
            get { return Wrapper.IsConnected; }
        }
        /// <summary>
        /// Получить биржевое время. Значение зависит от <see cref="P:StockSharp.Algo.BaseTrader.MarketTimeOffset"/>.
        /// </summary>
        public override DateTime MarketTime
        {
            get
            {
                return _wrapper !=null? _wrapper.MarketTime: _creationTime.AddMilliseconds(_elapsedTime.ElapsedMilliseconds);
            }
        }
        /// <summary>
        /// Имя пользователя в терминале Альфа-Директ
        /// </summary>
        public string Login { set; get; }
        /// <summary>
        /// Пароль для входа в терминал
        /// </summary>
        public string Password { set; private get; }
	    /// <summary>
		/// Отложенная инициализация AlfaWrapper
		/// </summary>
		private AlfaWrapper Wrapper
        {
            get
            {
                if (_wrapper == null)
                {
                    lock (_wrapperCreationMutex)
                    {
                        if (_wrapper == null)
                        {
                        	_wrapper = new AlfaWrapper();

							_creationTime = _wrapper.MarketTime;
							_elapsedTime.Start();

                            _wrapper.ConnectionErrorEvent += OnConnectionErrorEvent;
                            _wrapper.ConnectedEvent += OnWrapperConnectedEvent;
                            _wrapper.DisconnectedEvent += OnWrapperDisconnectedEvent;
                        	_wrapper.NewDataExportedEvent += OnWrapperNewDataExportedEvent;
                            _wrapper.ProcessDataErrorEvent += OnWrapperProcessDataErrorEvent;
                            _wrapper.OrdersRegisterFailedEvent += OnOrdersRegisterFailedEvent;
                            _wrapper.StopOrdersRegisterFailedEvent += OnStopOrdersRegisterFailedEvent;
                            _wrapper.OrdersCancelFailedEvent += OnOrdersCancelFailedEvent;
                            _wrapper.StopOrdersCancelFailedEvent += OnStopOrdersCancelFailedEvent;

							this.AddInfoLog("AlfaDirect v. {0}", _wrapper.Version);
                        }
                    }
                }

                return _wrapper;
            }
        }



	    /// <summary>
        /// Событие появления исторических тайм-фрейм свечек от Альфа-Директ.
        /// </summary>
        public event Action<CandleToken, IEnumerable<TimeFrameCandle>> CandlesStarted;
        /// <summary>
        /// Событие появления исторических тайм-фрейм свечек от Альфа-Директ.
        /// </summary>
        public event Action<CandleToken, IEnumerable<TimeFrameCandle>> CandlesChanged;
        /// <summary>
        /// Событие появления исторических тайм-фрейм свечек от Альфа-Директ.
        /// </summary>
        public event Action<CandleToken, IEnumerable<TimeFrameCandle>> CandlesFinished;



        /// <summary>
        /// Конструктор.
        /// </summary>
        public AlfaTrader()
        {
            Name = "AlfaTrader";
            IsSupportAtomicReRegister = false;
            TransactionIdGenerator = new RngCryptoTransactionIdGenerator();
        }

        #region Обработчики событий

        private void OnWrapperDisconnectedEvent()
	    {
            this.AddInfoLog("Соединение с Альфа-Директ разорвано");
            RaiseDisconnected();
	    }

	    private void OnWrapperConnectedEvent()
	    {
	        this.AddInfoLog("Соединение с Альфа-Директ установлено");
            RaiseConnected();
	    }

	    private void OnConnectionErrorEvent(Exception ex)
	    {
            this.AddInfoLog("Ошибка подключения: {0}".Put(ex.Message));
            RaiseConnectionError(ex);
	    }

		private void OnWrapperNewDataExportedEvent()
		{
			RaiseNewDataExported();
		}

        private void OnWrapperProcessDataErrorEvent(Exception ex)
        {
            RaiseProcessDataError(ex);
        }
        
        private void OnStopOrdersCancelFailedEvent(IEnumerable<OrderFail> orderFails)
        {
            RaiseStopOrdersCancelFailed(fails: orderFails);
        }

        private void OnOrdersCancelFailedEvent(IEnumerable<OrderFail> orderFails)
        {
            RaiseOrdersCancelFailed(fails: orderFails);
        }

        private void OnStopOrdersRegisterFailedEvent(IEnumerable<OrderFail> orderFails)
        {
            foreach (var orderFail in orderFails)
            {
                var order = orderFail.Order;
                _pendingOrders.Remove(order);
                order.State = OrderStates.Failed;
                RaiseOrderChanged(order);
            }
            
            RaiseStopOrdersRegisterFailed(fails: orderFails);
        }

        private void OnOrdersRegisterFailedEvent(IEnumerable<OrderFail> orderFails)
        {
            foreach (var orderFail in orderFails)
            {
                var order = orderFail.Order;
                _pendingOrders.Remove(order);
                order.State = OrderStates.Failed;
                RaiseOrderChanged(order);
            }

            RaiseOrdersRegisterFailed(fails: orderFails);
        }

        #endregion 

        /// <summary>
        /// Запустить экспорт данных из торговой системы в программу (получение портфелей, инструментов, заявок и т.д.).
        /// </summary>
        public override void StartExport()
        {
            this.AddInfoLog("StartExport()");

            if (IsExportStarted)
            {
                this.AddWarningLog("Export is already started");
                return;
            }

            base.StartExport();

            // Запускаем получение информации по Списку портфелей, Позициям, Списку инструментов, Ордерам, Своим сделкам.
            // Остальные данные будут получаться только после ручного запуска.
            RegisterPortfolios();
            RegisterPositions();
            RegisterSecurities();
            RegisterOrders();
            RegisterMyTrades();


            // Прочитаем все нужные таблицы чтобы заполнить BaseTrader данными. Если читать таблицы ДО подписки на них,
            // данные будут отфильтрованы и не будут вызваны обработчики. Нам же нужно вызвать обработчики для каждой таблицы.
            // Просто подписка не вернет данные по таблицам, которые не изменялись (потому что отслеживается событие изменения таблицы) 
            // так мы можем не получить нужного портфеля, ордеров и сделок за сегодня, что уже были. 
            // Порядок расположения методов важен, если ордера не прочитаны, сделки не будут корректно отработаны, итд.
            _wrapper.TblFinInfo.Update();
            _wrapper.TblBalance.Update();
            _wrapper.TblOrders.Update();
            _wrapper.TblMyTrades.Update();


            _candlesTimer = ThreadingHelper.Timer(OnProcessCandles).Interval(TimeSpan.FromSeconds(5));
        }
        /// <summary>
        /// Остановить экспорт данных из торговой системы в программу, запущенный через <see cref="M:StockSharp.BusinessEntities.ITrader.StartExport"/>.
        /// </summary>
        public override void StopExport()
        {
            this.AddInfoLog("StopExport()");

            if (!IsExportStarted)
            {
                this.AddWarningLog("Export is already stoped");
                return;
            }

            // удалим все существующие подписки на таблицы.
            // нужно отключить вообще все таблицы и удалить из них все подписки.
            UnRegisterAll();

            _candlesTimer.Dispose();

            base.StopExport();
        }

        #region Подписка/отписка на получение данных от терминала

        public void RegisterPortfolios()
        {
            this.AddInfoLog("RegisterPortfolios: starting export portfolios.");
            
            var where = new FilterRules();
            where.Add("p_code", "money");

            var sb = new Subscriber("portfolios", where, ProcessPortfolios);
            _wrapper.TblBalance.Subscribe(sb);
        }

        public void RegisterPositions()
        {
            // так как позицией является и бумаги и деньги, будем получать все данные как позиции
            // а фильтровать уже будем внутри обработчика. Нет возможности поставить фильтр "p_code != money"
            var where = new FilterRules();

            var sb = new Subscriber("positions", where, ProcessPositions);
            _wrapper.TblBalance.Subscribe(sb);
        }

        public void RegisterSecurities()
        {
            // получаем всю информацию по бумагам.
            var where = new FilterRules();

            var sb = new Subscriber("securities", where, ProcessSecurities);
            _wrapper.TblFinInfo.Subscribe(sb);
        }

        public void RegisterOrders()
        {
            // получаем всю информацию по Ордерам.
            var where = new FilterRules();

            var sb = new Subscriber("orders", where, ProcessOrders);
            _wrapper.TblOrders.Subscribe(sb);
        }

        public void RegisterMyTrades()
        {
            // получаем всю информацию по собственным сделкам
            var where = new FilterRules();

            var sb = new Subscriber("mytrades", where, ProcessMyTrades);
            _wrapper.TblMyTrades.Subscribe(sb);
        }

        public override void RegisterPortfolio(Portfolio portfolio)
        {
            var accCode = portfolio.Name.Split('@')[0];
            var placeCode = portfolio.Name.Split('@')[1];
            
            // получаем информацию только по определенному портфелю
            var where = new FilterRules();
            where.Add("acc_code", accCode);
            where.Add("place_code", placeCode);

            var sb = new Subscriber(portfolio.Name, where, ProcessPortfolios);
            _wrapper.TblBalance.Subscribe(sb);
        }

        public override void RegisterSecurity(Security security)
        {
            // получаем информацию только по определенному инструменту
            var where = new FilterRules();
            where.Add("paper_no", security.GetPaperNo());

            var sb = new Subscriber(security.GetPaperNo(), where, ProcessSecurities);
            _wrapper.TblFinInfo.Subscribe(sb);
        }

        public override void RegisterQuotes(Security security)
        {
            // получаем информацию только по определенному инструменту
            var where = new FilterRules();
            where.Add("paper_no", security.GetPaperNo());

            var sb = new Subscriber(security.GetPaperNo(), where, ProcessQuotes);
            _wrapper.TblQueue.Subscribe(sb);
        }

        public override void RegisterTrades(Security security)
        {
            // получаем информацию только по определенному инструменту
            var where = new FilterRules();
            where.Add("paper_no", security.GetPaperNo());

            var sb = new Subscriber(security.GetPaperNo(), where, ProcessTrades);
            _wrapper.TblAllTrades.Subscribe(sb);
        }



        public void UnRegisterPortfolios()
        {
            // удаляем все подписки на список портфелей.
            var subscribers = _wrapper.TblBalance.Subscribes.Where(sb => sb.ID == "portfolios");

            _wrapper.TblBalance.UnSubscribe(subscribers);
        }

        public void UnRegisterPositions()
        {
            // удаляем все подписки
            var subscribers = _wrapper.TblBalance.Subscribes.Where(sb => sb.ID == "positions");

            _wrapper.TblBalance.UnSubscribe(subscribers);
        }

        public void UnRegisterSecurities()
        {
            // удаляем все подписки
            var subscribers = _wrapper.TblFinInfo.Subscribes.Where(sb => sb.ID == "securities");

            // отписа изменяет коллекцию, отсюда форич невозможен для коллекции. .
            _wrapper.TblFinInfo.UnSubscribe(subscribers);
        }

        public void UnRegisterOrders()
        {
            // удаляем все подписки
            var subscribers = _wrapper.TblOrders.Subscribes.Where(sb => sb.ID == "orders");

            _wrapper.TblOrders.UnSubscribe(subscribers);
        }

        public void UnRegisterMyTrades()
        {
            // удаляем все подписки
            var subscribers = _wrapper.TblMyTrades.Subscribes.Where(sb => sb.ID == "mytrades");

            _wrapper.TblMyTrades.UnSubscribe(subscribers);
        }

        public override void UnRegisterPortfolio(Portfolio portfolio)
        {
            // удаляем все подписки
            var subscribers = _wrapper.TblBalance.Subscribes.Where(sb => sb.ID == portfolio.Name);

            _wrapper.TblBalance.UnSubscribe(subscribers);
        }

        public override void UnRegisterSecurity(Security security)
        {
            // удаляем все подписки
            var subscribers = _wrapper.TblFinInfo.Subscribes.Where(sb => sb.ID == security.GetPaperNo());

            _wrapper.TblFinInfo.UnSubscribe(subscribers);
        }

        public override void UnRegisterQuotes(Security security)
        {
            // удаляем все подписки
            var subscribers = _wrapper.TblQueue.Subscribes.Where(sb => sb.ID == security.GetPaperNo());

            _wrapper.TblQueue.UnSubscribe(subscribers);
        }

        public override void UnRegisterTrades(Security security)
        {
            // удаляем все подписки
            var subscribers = _wrapper.TblAllTrades.Subscribes.Where(sb => sb.ID == security.GetPaperNo());

            _wrapper.TblAllTrades.UnSubscribe(subscribers);
        }
        
        /// <summary>
        ///     Отписывает всех подписчиков от всех таблиц Альфы.
        /// </summary>
        /// <remarks>
        ///     Для отписки используется Reflections. Через аттрибут TblAttribute
        ///     находятся таблицы и вызываются их методы очистки от подписчиков.
        /// </remarks>
        private void UnRegisterAll()
        {
            // получаем список всех публичных свойств класса AlfaWrapper
            var propInfoes = typeof(AlfaWrapper).GetProperties(BindingFlags.Public | BindingFlags.Instance);

            // для каждого свойства, получаем набор пользовательских аттрибутов, и если аттрибут TblAttribute
            // значит это таблица. Вызываем метод отписки от таблицы и отписываем всех подписчиков от нее.
            foreach (var propertyInfo in propInfoes)
            {
                var attributes = propertyInfo.GetCustomAttributes(false);
                foreach (var attribute in attributes)
                {
                    if (attribute is TblAttribute)
                    {
                        var table = propertyInfo.GetValue(Wrapper, null);
                        (table as BaseTable).ClearSubscribers();
                    }
                }
            }
        }

        #endregion

        #region Обработка полученных данных от терминала

        /// <summary>
        /// Обработка списка портфелей.
        /// </summary>
        /// <param name="data"></param>
        private void ProcessPortfolios(TableData data)
        {
            this.AddInfoLog("OnProcessPortfolios() {0}", data.ToString());

            if (data.IsNull())
            {
                return;
            }

            ProcessEvents(() =>
            {
                foreach (var raw in data)
                {
                    // хотя в метод должны попадать только данные по позиции деньги, добавим дополнительную проверку.
                    if ((string)raw["p_code"] != "money")
                    {
                        continue;
                    }

                    var exchange = AlfaUtils.ExchangeCodeToExchange((string)raw["place_code"]);
                    var portfolioName = "{0}@{1}".Put((string)raw["acc_code"], (string)raw["place_code"]);

                    GetPortfolio(portfolioName,
                    name =>
                    {
                        var portfolio = EntityFactory.CreatePortfolio(name);

                        portfolio.Exchange = exchange;
                        portfolio.BeginAmount = raw["prev_close"].To<decimal>();
                        portfolio.CurrentAmount = raw["forword_rest"].To<decimal>();
                        return portfolio;
                    },
                    portfolio =>
                    {
                        portfolio.BeginAmount = raw["prev_close"].To<decimal>();
                        portfolio.CurrentAmount = raw["forword_rest"].To<decimal>();
                    });
                }
            });
        }
        /// <summary>
        /// Обработка списка позиций. Позицию Деньги обрабатывать не будем, она обрабатывается в портфелях.
        /// </summary>
        /// <param name="data"></param>
        private void ProcessPositions(TableData data)
        {
            this.AddInfoLog("OnProcessPositions() {0}", data.ToString());

            if (data.IsNull())
            {
                return;
            }


            ProcessEvents(() =>
            {
                foreach (var raw in data)
                {
                    // данные по позициям Деньги будут попадать сюда, так как деньги, тоже позиция
                    // но обрабатывать здесь ее не будем. Обработка ведется в портфелях.
                    if ((string)raw["p_code"] == "money")
                    {
                        continue;
                    }

                    var portf = GetPortfolio("{0}@{1}".Put((string)raw["acc_code"], (string)raw["place_code"]));
                    // если инструментов еще нет в трейдере то будет создан пустой инструмент.
                    // это надо учитывать при парсинге инструментов, может быть уже созданный, но с пустыми базовыми полями.
                    var secur = GetSecurity((string)raw["paper_no"]);

                    GetPosition(portf, secur,
                    (portfolio, security) =>
                    {
                        var position = EntityFactory.CreatePosition(portfolio, security);
                        // Чистая позиция(или форвардная) – после урегулирования всех обязательств 
                        // (сделки РЕПО, сделки на вечерке, неурегулированные сделки на РТС стандарте, неурегулированные переводы,платежи)
                        position.CurrentValue = raw["forword_rest"].To<long>();
                        // при установке стопа с TargetProfit значение netto_rest уменьшается на размер стопа.
                        // считаем это уменьшение = BlockedValue. 
                        // TODO: прояснить до конца вопрос с этим параметром. Что будем считать блокированным объемом.
                        position.BlockedValue = 0;
                        return position;
                    },
                    position =>
                    {
                        position.CurrentValue = raw["forword_rest"].To<long>();
                        position.BlockedValue = 0;
                    });
                }
            });
        }
        /// <summary>
        /// Обработка списка инструментов, по которым приходят данные в терминал.
        /// </summary>
        /// <param name="data"></param>
        private void ProcessSecurities(TableData data)
        {
            this.AddInfoLog("OnProcessSecurities {0}", data.ToString());

            if (data.IsNull())
            {
                return;
            }


            ProcessEvents(() =>
            {
                foreach (var raw in data)
                {
                    #region Определение локальных переменных

                    var p_code = (string) raw["p_code"];
                    var place_code = (string)raw["place_code"];
                    var id = "{0}@{1}".Put(p_code, place_code);
                    var ANSI_name = (string)raw["ANSI_name"];
                    var mat_date = DateTime.Parse((string) raw["mat_date"]);
                    var state = AlfaUtils.SecurityStateFromAlfa((string) raw["status"]);
                    var exchangeCode = GetExchangeCode(place_code);
                    var exchange = AlfaUtils.ExchangeCodeToExchange(exchangeCode);
                    var go_buy = raw["go_buy"].To<decimal>();
                    var go_sell = raw["go_sell"].To<decimal>();
                    var paper_no = (string)raw["paper_no"];
                    var curr_code = (string)raw["curr_code"];
                    var board_code = (string)raw["board_code"];
                    var securityType = AlfaUtils.BoardCodeToSecurityType(board_code);
                    var open_pos_qty = raw["open_pos_qty"].To<long>();
                    var open_price = raw["open_price"].To<decimal>();
                    var close_price = raw["close_price"].To<decimal>();

                    #endregion Определение локальных переменных

                    GetSecurity(paper_no, name =>
                    {
                        #region создание инструмента
                        
                            this.AddInfoLog("Security create : {0}", raw.ToString());
                        
                            var security = EntityFactory.CreateSecurity(id);
                            security.ExtensionInfo = new Dictionary<object, object>();

                            security.Name = ANSI_name;
                            security.ShortName = ANSI_name;
                            security.ExpiryDate = mat_date;
                            security.Code = p_code;
                            security.State = state;
                            security.Exchange = exchange;
                            security.MarginBuy = go_buy;
                            security.MarginSell = go_sell;

                            security.SetPaperNo(paper_no);
                            security.SetCurrency(curr_code);
                            security.SetPlaceCode(place_code);

                            decimal priceStep, priceStepCost;
                            GetPriceStepInfo(security, out priceStep, out priceStepCost);
                            security.MinStepSize = priceStep;

                            if (priceStepCost == 0)
                            {
                                security.MinStepPrice = priceStep; // On Micex it's the same
                                this.AddWarningLog("Стоимость шага цены равна нулю для {0}.", security.Name);
                            }
                            else
                            {
                                security.MinStepPrice = priceStepCost;
                            }
                            
                            security.Type = securityType;
                            security.SetOpenInteres(open_pos_qty);

                            security.OpenPrice = open_price;
                            security.ClosePrice = close_price;

                            return security;

                        #endregion создание инструмента
                    }, 
                        security =>
                        {
                            #region Обновление инструмента

                            // очень важный момент который нужно помнить это то, что если парсинг позиций например
                            // пройдет раньше чем парсинг инструментов, то будут созданы болванки инструментов с пустыми полями.
                            // значит нужно обновлять все поля которые мы заполняем при создании инструмента.
                            this.AddInfoLog("Security update {0}.", security.Id);

                            security.Id = id;
                            security.Name = ANSI_name;
                            security.ShortName = ANSI_name;
                            security.ExpiryDate = mat_date;
                            security.Code = p_code;
                            security.State = state;
                            security.Exchange = security.Exchange = exchange;
                            security.MarginBuy = go_buy;
                            security.MarginSell = go_sell;

                            security.SetPaperNo(paper_no);
                            security.SetCurrency(curr_code);
                            security.SetPlaceCode(place_code);

                            decimal priceStep, priceStepCost;
                            GetPriceStepInfo(security, out priceStep, out priceStepCost);
                            security.MinStepSize = priceStep;

                            if (priceStepCost == 0)
                            {
                                security.MinStepPrice = priceStep; // On Micex it's the same
                            }
                            else
                            {
                                security.MinStepPrice = priceStepCost;
                            }

                            security.Type = securityType;
                            

                            security.BestAsk = new Quote
                            {
                                Price = raw["sell"].To<decimal>(),
                                Volume = raw["sell_qty"].To<decimal>(),
                                Security = security,
                                OrderDirection = OrderDirections.Sell
                            };

                            security.BestBid = new Quote
                            {
                                Price = raw["buy"].To<decimal>(),
                                Volume = raw["buy_qty"].To<decimal>(),
                                Security = security,
                                OrderDirection = OrderDirections.Buy
                            };

                            security.LowPrice = raw["min_deal"].To<decimal>();
                            security.HighPrice = raw["max_deal"].To<decimal>();
                            security.SetOpenInteres(open_pos_qty);

                            security.OpenPrice = open_price;
                            security.ClosePrice = close_price;

                            security.MinLotSize = raw["lot_size"].To<int>();
 
                            // TODO: Update security state
                            // TODO: Add missing fields

                            security.RaisePropertyChanged();
                            //RaiseSecuritiesChanged(new[] { security });

                            #endregion Обновление инструмента
                        },
                        null);
                }
            });

            // TODO: Add missing Security fields
            /*
            SecurityFields = "paper_no, ts_p_code, board_code, sell, sell_qty, buy, buy_qty, min_deal, max_deal, lot_size, decimals, status, price_step, p_code, volatility, theor_price, mat_date";
			
            security.MinLotSize = details[9].To<int>();
            security.MinStepSize = details[10].To<decimal>();
            //security.State = details[11].To<int>();
            security.MinStepPrice = details[12].To<decimal>();
            security.UnderlyingSecurityId = details[13];
            security.Volatility = details[14].To<decimal>();
            security.TheorPrice = details[15].To<decimal>();
            */
        }
        /// <summary>
        /// Обработка ордеров. Ордера с TakeProfit обрабатываются криво и не  стоит их пока использовать.
        /// </summary>
        /// <param name="data"></param>
        private void ProcessOrders(TableData data)
        {
            this.AddInfoLog("OnProcessOrders() {0}", data.ToString());

            if (data.IsNull()) return;


            ProcessEvents(() =>
            {
                foreach (var raw in data)
                {
                    #region Получение базовых значений ордера
                    // 83487901|41469-000|42550|M|B|8356|1|1|0:00:00||FORTS|0|
                    // ...
                    // 83487901|41469-000|42550|M|B|8356|1|0|12.12.2011 16:31:41|2198931532|FORTS|0|
                    // NOTE: когда в первый раз приходит апдейт по ордеру в нем нет ни времени, ни комментария.

                    // 84352688|41469-000|12910|M|S|80.13|10|0|22.12.2011 17:16:58||MICEX_SHR|0|0|
                    // 84352688|41469-000|12910|M|S|80.13|10|0|22.12.2011 17:16:58|2968835969|MICEX_SHR|0|80.408|
                    // NOTE: на ММВБ ордер сперва приходит с пустым комментарием!
                  
                    var portfolio   = GetPortfolio("{0}@{1}".Put((string)raw["acc_code"], (string)raw["place_code"]));
                    var security    = GetSecurity((string)raw["paper_no"]);
                    var ordNo       = raw["ord_no"].To<long>();
                    var orderClass  = ((string)raw["blank"]).ToUpper();    // тип заявки. Стоп, лимит, трейлинг стоп итд.
                    var stopPrice   = raw["stop_price"].To<decimal>();
                    var price       = raw["price"].To<decimal>();

                    
                    long transactionId = 0;
                    // Пробуем вытащить номер из заявки, сохраненной  при регистрации ордера.
                    // Суть в том, что во враппере ордеру присваивается номер, как только приходит подтверждение регистрации от биржи.
                    // Отсюда мы можем найти среди ожидающих ордеров нужный нам ордер и взять номер транзакции из него.
                    var pendingOrder = _pendingOrders.FirstOrDefault(o => o.Id == ordNo);
                    if (pendingOrder != null)
                    {
                        _pendingOrders.Remove(pendingOrder);
                        transactionId = pendingOrder.TransactionId;
                    }
                    else
                    {
                        // Ну если ордер не был найден среди ожидающих подтверждение, значит попробуем взять номер транзакции из поля комментария.
                        // Поле комментария может быть не только цифровым.Может быть любым если ордер создавался руками. 
                        // Учтем возможный вызов исключения
                        try
                        {
                            transactionId = raw["comments"].To<long>();
                        }
                        catch (Exception ex)
                        {
                            var exception = new Exception("Ошибка преобразования поля comments в long формат.", ex);
                            RaiseProcessDataError(exception);
                        }
                    }

                    // если мы не нашли номер транзакции значит ордер не может быть обработан правильно.
                    if (transactionId == 0)
                    {
                        this.AddErrorLog("Не найден TransactionId для ордера {0}. Ордер не может быть обработан.", ordNo);
                        return;
                    }

                    #endregion

                    // создаем новый ордер, или вызываем обновление ордера если ордер найден.
                    // Если ордер не найден, будет сначала вызван метод для создания ордера, затем будет вызван метод для изменения ордера.
                    // Если ордер был создан новый, то автоматом сгенерится событие NewOrder, без OrdersChanged. Если ордер был найден, то отработает только
                    // метод для изменения ордера и будет вызвано событие OrdersChanged
                    GetOrder(security, ordNo, id =>
                    {
                        #region Метод создания нового ордера

                        this.AddInfoLog("create order: id = {0}, tr. id = {1}", id, transactionId);
                        
                        // для простого стопа или трейлинг стопа ищем среди стоп заявок нужную заявку. Для простых заявок ищем среди простых
                        var order = (orderClass == "T" || orderClass == "S") ? GetStopOrderByTransactionId(transactionId) : GetOrderByTransactionId(transactionId);

                        if (order.IsNull())
                        {
                            this.AddInfoLog("Создание ордера из терминала, tr. id {0}", transactionId);
                            order = EntityFactory.CreateOrder(id);
                            order.TransactionId = transactionId;
                        }

                        order.Trader = this;
                        order.Portfolio = portfolio;
                        order.Security = security;

                        // TODO: нормальной обработки стопов с TakeProfit нет пока. Нужно добавлять.
                        // Заполним стоп поля для заявок, которые не будут изменяться больше.
                        switch (orderClass)
                        {
                            // лимит заявка
                            case "L":
                                order.Type = OrderTypes.Limit;
                                break;
                            // простая стоп заявка
                            case "S":
                                order.Type = OrderTypes.Conditional;
                                order.StopCondition = new AlfaStopCondition()
                                                          {
                                                              Slippage = (double)Math.Abs(stopPrice - price),
                                                              StopPrice = (double)stopPrice,
                                                          };
                                break;
                            // трейлинг стоп/следящая заявка
                            case "T":
                                order.Type = OrderTypes.Conditional;
                                order.StopCondition = new AlfaStopCondition()
                                                          {
                                                              Slippage = (double)Math.Abs(stopPrice - price),
                                                              StopPrice = (double)stopPrice,
                                                              TrailingLevel = raw["trailing_level"].To<double?>(),
                                                          };
                                break;
                        }

                        order.RaisePropertyChanged();

                        return order;

                        #endregion
                    },

                    order =>
                    {
                        #region Метод обновления ордера

                        this.AddInfoLog("update order: id = {0}", order.Id);

                        order.Id        = raw["ord_no"].To<long>();
                        order.Direction = AlfaUtils.OrderDirectionsFromAlfa((string)raw["b_s"]);
                        order.Price     = raw["price"].To<decimal>();
                        order.Volume    = raw["qty"].To<Int32>();
                        order.Balance   = raw["rest"].To<Int32>();
                        order.Time      = DateTime.Parse((string)raw["ts_time"]);
                        order.ExpiryDate = DateTime.Parse((string)raw["drop_time"]);
                        order.SetAlfaAveragePrice(raw["avg_trd_price"].To<decimal>());

                        
                        // TODO: нормальной обработки стопов с TakeProfit нет пока. Нужно добавлять.
                        var stopCond = (AlfaStopCondition)order.StopCondition;
                        // поскольку параметры трейлинг стопа со временем изменяются, нужно обновить их в заявке тоже.
                        // для лимит и стоп заявки параметры не меняются в ходе работы.
                        // для заявки с TakeProfit изменяются, но мы пока этот тип заявок не понимаем.
                        switch (orderClass)
                        {
                            // трейлинг стоп/следящая заявка
                            case "T":
                                if (stopPrice > 0)
                                    stopCond.StopPrice = (double)stopPrice;
                              
                                break;
                        }

                        // заполним статус заявки
                        switch ((string)raw["status"]) // статус
                        {
                            case "O": // активная
                                order.State = OrderStates.Active;
                                break;
                            //case "G": // с условием
                            case "M": // исполнена
                                if (order.Balance != 0)
                                    order.State = OrderStates.Active;
                                else if (order.GetAlfaAveragePrice() != 0)
                                    order.State = OrderStates.Done;
                                break;

                            case "W": // удалена
                                order.State = OrderStates.Done;
                                // Если время отмены уже существует, то оставляем его. Иначе стоп старт экспорта, обновит это время для ордера
                                // поскольку все ордера будут прочитаны заново.
                                order.CancelTime = order.CancelTime ?? this.MarketTime;
                                break;
 
                            default:
                                this.AddInfoLog("Order status {0} is not taken into account", (string)raw["status"]);
                                break;
                        }


                        #region Эмуляция стоп заявки Quik для правильной работы стратегий.
                        // для стоп заявки , мы автоматически добавляем DerivedOrder
                        // в квике например, стоп заявка порождает другую заявку, лимитную с другим номером.
                        // отсюда S# имеет заточку под это дело. В альфе же заявка одна, просто активация заявки
                        // имеет тот же смысл что и выставление нового лимитного ордера в квике.
                        // DerivedOrder используется в стратегиях для учета сделок по стоп заявкам например.
                        // PositionManager реагирует только на лимитные заявки, по событию NewOrder, OrdersChanged, NewMyTrades, то есть не реагирует воообще на стопордера.
                        // Стратегия по событию изменения стоп заявки, если у нее есть DerivedOrder добавляет этот ордер в список простых ордеров стратегии. Ну а дальше
                        // Если приходит событие от трейдера по изменению этой заявки, сработает позишн манагер и посчитает изменение позиции.
                        // То есть событие изменения Стоп заявки обязательно должно произойти иначе Derived ордер не будет посчитан стратегией.
                        if(order.Type == OrderTypes.Conditional && ((order.State == OrderStates.Active) || (order.State == OrderStates.Done)))
                        {
                            if (order.DerivedOrder.IsNull())
                            {
                                // создаем ордер связанный и генерим событие о новом ордере
                                var transId      = TransactionIdGenerator.GetNextId();
                                var derivedOrder = EntityFactory.CreateOrder(transId);

                                derivedOrder.Trader     = order.Trader;
                                derivedOrder.Portfolio  = order.Portfolio;
                                derivedOrder.Security   = order.Security;
                                
                                derivedOrder.Direction  = order.Direction;
                                derivedOrder.Type       = OrderTypes.Limit;

                                derivedOrder.State      = order.State;
                                derivedOrder.Status     = order.Status;

                                derivedOrder.Price      = order.Price;
                                derivedOrder.Balance    = order.Balance;
                                derivedOrder.Volume     = order.Volume;

                                derivedOrder.Time       = order.Time;
                                derivedOrder.CancelTime = order.CancelTime;
                                derivedOrder.ExpiryDate = order.ExpiryDate;
                                
                                derivedOrder.Comment    = transId.ToString(CultureInfo.InvariantCulture);
                                derivedOrder.Latency    = order.Latency;
                                derivedOrder.Messages   = order.Messages;

                                derivedOrder.ExecutionCondition = order.ExecutionCondition;
                                derivedOrder.ExtensionInfo      = order.ExtensionInfo;
                                derivedOrder.RepoInfo           = order.RepoInfo;
                                derivedOrder.RpsInfo            = order.RpsInfo;

                                order.DerivedOrder = derivedOrder;
                                RaiseNewOrder(derivedOrder);
                            }
                            else
                            {
                                // изменяем ордер и генерим событие об изменении ордера.
                                var derivedOrder        = order.DerivedOrder;

                                derivedOrder.State      = order.State;
                                derivedOrder.Status     = order.Status;

                                derivedOrder.Price      = order.Price;
                                derivedOrder.Balance    = order.Balance;
                                derivedOrder.Volume     = order.Volume;

                                derivedOrder.CancelTime = order.CancelTime;
                                derivedOrder.ExpiryDate = order.ExpiryDate;

                                derivedOrder.Latency    = order.Latency;
                                derivedOrder.Messages   = order.Messages;

                                derivedOrder.ExecutionCondition = order.ExecutionCondition;
                                derivedOrder.ExtensionInfo      = order.ExtensionInfo;
                                derivedOrder.RepoInfo           = order.RepoInfo;
                                derivedOrder.RpsInfo            = order.RpsInfo;

                                RaiseOrderChanged(derivedOrder);
                            }
                        }

                        #endregion

                        order.RaisePropertyChanged();

                        #endregion
                    });
                }
            });
        }
        /// <summary>
        /// Обработка собственных сделок.
        /// </summary>
        /// <param name="data"></param>
        private void ProcessMyTrades(TableData data)
        {
            this.AddInfoLog("OnProcessMyTrades() {0}", data.ToString());

            if (data.IsNull())
            {
                return;
            }

            ProcessEvents(() =>
            {
                foreach (var raw in data)
                {
                    var security = GetSecurity((string)raw["paper_no"]);
                    var tradeId = raw["trd_no"].To<long>();
                    var orderId = raw["ord_no"].To<long>();

                    if (orderId == 0)
                    {
                        this.AddWarningLog("Сделка с номером {0} имеет нулевой номер иниц. заявки", tradeId);
                        continue;
                    }

                    AddMyTrade(security, orderId, 0, tradeId, id =>
                    {
                        var trade = EntityFactory.CreateTrade(id);

                        trade.Price = raw["price"].To<decimal>();
                        trade.Security = security;
                        trade.Time = DateTime.Parse((string)raw["ts_time"]);
                        trade.Volume = raw["qty"].To<decimal>();
                        trade.OrderDirection = AlfaUtils.OrderDirectionsFromAlfa((string)raw["b_s"]);

                        return trade;
                    }, 
                    t => 
                    { 
                        // TODO: добавить обновление данных по сделке.
                    });
                }
            });
        }
        /// <summary>
        /// Обработка стакана.
        /// </summary>
        /// <param name="data"></param>
        private void ProcessQuotes(TableData data)
        {
            if (data.Count == 0)
            {
                return;
            }
            var paperNo = (string)data[0]["paper_no"];
            var security = GetSecurity(paperNo);

            this.AddInfoLog("OnProcessQuotes(): {0}", security.Id);


            ProcessEvents(() =>
            {
                var bids = new List<Quote>();
                var asks = new List<Quote>();

                foreach (var raw in data)
                {
                    // quoteStr: 0|10138|14|
                    var sellQty = raw["sell_qty"].To<decimal>();
                    var price = raw["price"].To<decimal>();
                    var buyQty = raw["buy_qty"].To<decimal>();

                    var quote = new Quote
                    {
                        Price = price,
                        Security = security
                    };

                    if (sellQty == 0)
                    {
                        quote.OrderDirection = OrderDirections.Buy;
                        quote.Volume = buyQty;

                        bids.Insert(0, quote);
                    }
                    else if (buyQty == 0)
                    {
                        quote.OrderDirection = OrderDirections.Sell;
                        quote.Volume = sellQty;

                        asks.Add(quote);
                    }

                    // If the sellQty and buyQty are 0 - that is our limit order 
                    // which is not a part of the market depth. Just skip it.
                }

                var marketDepth = GetMarketDepth(security);
                marketDepth.Update(bids, asks, true);

                RaiseQuotesChanged(new[] { marketDepth });

                security.BestAsk = marketDepth.BestAsk;
                security.BestBid = marketDepth.BestBid;
                RaiseSecuritiesChanged(new[] { security });
            });
        }
        /// <summary>
        /// Обработка тиковых сделок.
        /// </summary>
        /// <param name="data"></param>
        private void ProcessTrades(TableData data)
        {
            // params: paper_no = 36955
            var paperNo = (string)data[0]["paper_no"];
            var security = GetSecurity(paperNo);

            this.AddInfoLog("OnProcessTrades(): {0}", security.Id);

            // "trd_no, paper_no, qty, price, ts_time, b_s";
            // 274847059|36955|5|9930|10:10:11|0|
            // 0 - покупка, 1 - продажа

            //Logger.Debug(data);


            ProcessEvents(() =>
            {
                foreach (var raw in data)
                {
                    GetTrade(security, raw["trd_no"].To<long>(), id =>
                    {
                        var trade = EntityFactory.CreateTrade(id);

                        trade.Security = security;
                        trade.OrderDirection = AlfaUtils.OrderDirectionsFromAlfa((string)raw["b_s"]);
                        trade.Price = raw["price"].To<decimal>();
                        trade.Time = DateTime.Parse((string)raw["ts_time"]);
                        trade.Volume = raw["qty"].To<decimal>();

                        security.LastTrade = trade;

                        security.RaisePropertyChanged();
                        RaiseSecuritiesChanged(new[] { security });

                        return trade;
                    });
                }
            });
        }

        #endregion

        /// <summary>
        /// Возвращает Код биржи по коду рынка.
        /// </summary>
        /// <param name="placeCode">Код рынка.</param>
        /// <returns>Код биржи.</returns>
        private string GetExchangeCode(string placeCode)
        {
            // читаем таблицу Справочник бирж, и находим биржу с нашим кодом.
            var data = Wrapper.TblTradePlaces.Read();
            var tradePlace = data.FirstOrDefault(tp => (string)tp["place_code"] == placeCode);

            if (tradePlace == null)
            {
                throw new Exception("Не удалось определить код биржи для place_code={0}.".Put(placeCode));
            }

            return (string)tradePlace["ex_code"];
        }
        /// <summary>
        /// Получить информацию о минимальном шаге и его стоимости для заданного инструмента.
        /// </summary>
        /// <param name="security">Инструмент.</param>
        /// <param name="step">Минимальный шаг цены.</param>
        /// <param name="cost">Стоимость шага цены.</param>
        private void GetPriceStepInfo(Security security, out decimal step, out decimal cost)
        {
            step = 0;
            cost = 0;

            var data = Wrapper.TblPapers.Read();
            if (data.Count == 0)
            {
                this.AddWarningLog("Failed to get price step info for the {0} data is null".Put(security.Code));
                return;
            }

            // получаем строку с интересующим нас инструментом и парсим его значения.
            var paper = data.FirstOrDefault(p => (string)p["paper_no"] == security.GetPaperNo());
            if (paper != null)
            {
                step = Decimal.Parse((string)paper["price_step"], NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint);
                cost = Decimal.Parse((string)paper["price_step_cost"], NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint);
            }
            else
            {
                this.AddWarningLog("Failed to get price step info for the {0} data is null".Put(security.Code));
            }
        }



		private void OnProcessCandles()
		{
			if (!IsConnected || !IsExportStarted)
			{
				this.AddWarningLog("Failed to process candles");
				return;	
			}

			//this.AddInfoLog("Start process candles");

			_candleTokens.SyncDo(dictionary =>
			{
				var keys = new List<CandleToken>(dictionary.Keys);

				foreach (var token in keys)
				{
					var timeInfo = dictionary[token];
					var timeFrame = (AlfaTimeFrames)token.Arg;

					//var finishTime = timeInfo.TimeStarted + (TimeSpan)timeFrame;
					var finishTime = MarketTime + (TimeSpan)timeFrame;

					ProcessHistoryCandles(token, timeFrame, new Range<DateTime>(timeInfo.TimeStarted, finishTime), false, ref timeInfo);

					dictionary[token] = timeInfo;
				}
			});

			//this.AddInfoLog("End process candles");
		}

        #region Регистрация/отмена ордеров

        // TODO: рассмотреть и решить проблему двойной регистрации ордера. Ордер регистрированный может быть еще раз подан в регистрацию.
        /// <summary>
		/// Зарегистрировать заявку на бирже.
		/// </summary>
		/// <param name="order">Заявка, содержащая информацию для регистрации.</param>
		protected override void OnRegisterOrder(Order order)
		{
			this.AddInfoLog("Регистрация ордера tr.id = {0}", order.TransactionId);
			// OnProcessOrders will read transaction id from the comment
			order.Comment = order.TransactionId.ToString(CultureInfo.InvariantCulture);

            // сохраняем ордер. Сделано это для того, чтобы в OnProcessOrders, когда приходит
            // первый раз ордер без comments и времени, можно было найти номер транзакции.
            _pendingOrders.Add(order);

		    if (order.IsLimit() || order.IsMarket())
		    {
                //Wrapper.CreateLimitOrder(order);
                Wrapper.AsyncCreateLimitOrder(order);
		    }
            else if (order.IsStop())
            {
                //Wrapper.CreateStopOrder(order);
                Wrapper.AsyncCreateStopOrder(order);
            }
            else if (order.IsTrailingStop())
            {
                //Wrapper.CreateTrailingOrder(order);
                Wrapper.AsyncCreateTrailingOrder(order);
            }
            else
            {
                throw new Exception("Ордер неизвестного типа. Регистрация невозможна.");
            }
		}
		/// <summary>
		/// Перерегистрировать заявку на бирже. 
		/// На текущий момент условные заявки просто сменяют одна другую. Страя удаляется, новая создается.
		/// </summary>
		/// <param name="oldOrder">Заявка, которую нужно снять.</param>
		/// <param name="newOrder">Новая заявка, которую нужно зарегистрировать.</param>
		protected override void OnReRegisterOrder(Order oldOrder, Order newOrder)
		{
			//if (oldOrder.Security.Exchange == Exchange.Rts)
			//{
            //    Wrapper.ReRegisterOrder(oldOrder, newOrder);
			//}
			//else
			//{
			//	base.OnReRegisterOrder(oldOrder, newOrder);
			//}

            // проверку входных параметров делать не нужно. Она делается в базовом классе в ReRegisterOrder.
            
            // TODO: ReRegisterOrder базового класса для условных заявок делает отмену и создание новой. Переопределить базовый метод.
            if (oldOrder.IsLimit() || oldOrder.IsMarket())
            {
                //Wrapper.UpdateLimitOrder(oldOrder, newOrder);
                Wrapper.AsyncUpdateLimitOrder(oldOrder, newOrder);
            }
            else if (oldOrder.IsStop()) // сюда никогда не попадем, для условных заявок не вызывается этот метод.
            {
                //Wrapper.UpdateStopOrder(oldOrder, newOrder);
                Wrapper.AsyncUpdateStopOrder(oldOrder, newOrder);
            }
            else if (oldOrder.IsTrailingStop())
            {
                //Wrapper.UpdateTrailingOrder(oldOrder, newOrder);
                Wrapper.AsyncUpdateTrailingOrder(oldOrder, newOrder);
            }
            else
            {
                throw new Exception("Ордер неизвестного типа. Регистрация невозможна.");
            }

		}
		/// <summary>
		/// Отменить заявку на бирже.
		/// </summary>
		/// <param name="order">Заявка, которую нужно отменять.</param>
		protected override void OnCancelOrder(Order order)
		{
			this.AddInfoLog("Canceling order: id = {0}, tr. id = {1}".Put(order.Id, order.TransactionId));

            // поскольку все заявки ставятся в очередь, то при постановки и сразу же отмене заявки, в этом месте мы не будем знать
            // какой у заявки id. Но если заявка не будет отменена мы получим событие об ошибке снятия заявки.
			//if (order.Id == 0)
			//{
			//	this.AddErrorLog("Failed to cancel an order which is not active yet.");
			//	return;
			//}

            //Wrapper.CancelOrder(order);
            Wrapper.AsyncCancelOrder(order);
		}
		/// <summary>
		/// Отменить группу заявок на бирже по фильтру.
		/// </summary>
		/// <param name="isStopOrder">True, если нужно отменить только стоп-заявки, false - если только обычный и null - если оба типа.</param>
		/// <param name="portfolio">Портфель. Если значение равно null, то портфель не попадает в фильтр снятия заявок.</param>
		/// <param name="direction">Направление заявки. Если значение равно null, то направление не попадает в фильтр снятия заявок.</param>
		/// <param name="classCode">Код класса. Если переданная строка пустая, то код не попадает в фильтр снятия заявок.</param>
		/// <param name="security">Инструмент. Если значение равно null, то инструмент не попадает в фильтр снятия заявок.</param>
		public override void CancelOrders(bool? isStopOrder = null, Portfolio portfolio = null, OrderDirections? direction = null, string classCode = null, Security security = null)
		{
			Wrapper.CancelOrders(isStopOrder, portfolio, direction, classCode, security);
		}

        #endregion

        /// <summary>
		/// Подключиться к торговой системе.
		/// </summary>
		protected override void OnConnect()
		{
			this.AddInfoLog("OnConnect()");

            if (Wrapper.IsConnected)
            {
                OnWrapperConnectedEvent();
			}
            else
            {
                Wrapper.Connect(Login, Password);                
            }
		}
		/// <summary>
		/// Отключиться от торговой системы.
		/// </summary>
		protected override void OnDisconnect()
		{
			this.AddInfoLog("OnDisconnect()");

            if (! Wrapper.IsConnected)
            {
                RaiseDisconnected();
            }
            else
            {
                Wrapper.Disconnect();
            }
		}


    	/// <summary>
		/// Получить исторические данные (тайм-фрейм свечки) от сервера Альфа-Директ.
		/// </summary>
		/// <param name="security">Инструмент, для которого необходимо получить исторические данные.</param>
		/// <param name="timeFrame">Тайм-фрейм.</param>
		/// <param name="range">Диапазон времени, для которого нужно получить данные.</param>
		/// <returns>Исторические данные. Время свечки выставляется по закрытию свечи.</returns>
		public IEnumerable<TimeFrameCandle> GetHistoryData(Security security, AlfaTimeFrames timeFrame, Range<DateTime> range)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (timeFrame == null)
				throw new ArgumentNullException("timeFrame");

			if (range == null)
				throw new ArgumentNullException("range");

			this.AddInfoLog("GetHistoryData: security = {0}", security.Id);

			var data = _wrapper.GetHistoryData(security, timeFrame, range);

            var result = new List<TimeFrameCandle>();

			if(data != null)
			{
				this.AddInfoLog(data);

				var candlesStr = data.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

				foreach (var str in candlesStr)
				{
					var details = str.Split('|');

					var candle = new TimeFrameCandle
					{
						Security = security,
						OpenPrice = details[1].To<decimal>(),
						HighPrice = details[2].To<decimal>(),
						LowPrice = details[3].To<decimal>(),
						ClosePrice = details[4].To<decimal>(),
						TotalVolume = details[5].To<decimal>(),
						TimeFrame = (TimeSpan)timeFrame,
						Time = DateTime.Parse(details[0])
					};

					result.Add(candle);
				}				
			}

			return result;
		}

		/// <summary>
		/// Получить исторические данные (тайм-фрейм свечки) из локальной базы Альфа-Директ.
		/// </summary>
		/// <param name="security">Инструмент, для которого необходимо получить исторические данные.</param>
		/// <param name="timeFrame">Тайм-фрейм.</param>
		/// <param name="range">Диапазон времени, для которого нужно получить данные.</param>
        /// <returns>Исторические данные. Время свечки выставляется по закрытию свечи.</returns>
        public IEnumerable<TimeFrameCandle> GetLocalHistoryData(Security security, AlfaTimeFrames timeFrame, Range<DateTime> range)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (timeFrame == null)
				throw new ArgumentNullException("timeFrame");

			if (range == null)
				throw new ArgumentNullException("range");

			this.AddInfoLog("GetLocalHistoryData: {0}, {1} - {2}", security.Id, range.Min, range.Max);

			var data = _wrapper.GetLocalHistoryData(security, timeFrame, range);

            var result = new List<TimeFrameCandle>();

			if (data != null)
			{
				this.AddInfoLog(data);

				var candlesStr = data.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

				foreach (var str in candlesStr)
				{
					var details = str.Split('|');

					var candle = new TimeFrameCandle
					{
						Security = security,
						OpenPrice = details[1].To<decimal>(),
						HighPrice = details[2].To<decimal>(),
						LowPrice = details[3].To<decimal>(),
						ClosePrice = details[4].To<decimal>(),
						TotalVolume = details[5].To<decimal>(),
						TimeFrame = (TimeSpan)timeFrame,
						Time = DateTime.Parse(details[0])
					};

					result.Add(candle);
				}
			}

			return result;
		}


        /// <summary>
		/// Начать получать тайм-фрейм свечки реального времени от Альфа-Директ.
        /// </summary>
        /// <remarks>
        /// После окончания получения исторических свечек возвращаются свечки реального времени.
        /// </remarks>
        /// <param name="security">Инструмент, для которого необходимо начать получать исторические свечки.</param>
        /// <param name="timeFrame">Тайм-фрейм.</param>
        /// <returns>Идентификатор непрерывного получения свечек. Необходим для отмены через метод <see cref="UnRegisterCandles"/>.</returns>
        public CandleToken RegisterCandles(Security security, AlfaTimeFrames timeFrame)
		{
            return RegisterCandles(security, timeFrame, MarketTime);
        }
        /// <summary>
        /// Начать получать непрерывно исторические тайм-фрейм свечки от сервера AlfaDirect, а так же свечки реального времени.
        /// </summary>
        /// <remarks>
        /// После окончания получения исторических свечек возвращаются свечки реального времени.
        /// </remarks>
        /// <param name="security">Инструмент, для которого необходимо начать получать исторические свечки.</param>
        /// <param name="timeFrame">Тайм-фрейм.</param>
        /// <param name="from">Временная точка отсчета, с которой необходимо начать получать свечки.</param>
        /// <returns>Идентификатор непрерывного получения свечек. Необходим для отмены через метод <see cref="UnRegisterCandles"/>.</returns>
        public CandleToken RegisterCandles(Security security, AlfaTimeFrames timeFrame, DateTime from)
			{
            if (security == null)
                throw new ArgumentNullException("security");

            if (timeFrame == null)
                throw new ArgumentNullException("timeFrame");

            var timeSpan = (TimeSpan)timeFrame;

            var to = timeSpan.GetCandleBounds(this).Min;

            var token = new AlfaCandleToken(security, timeFrame);

            var timeInfo = new CandleTimeInfo
            {
                TimeStarted = to
            };

            ProcessHistoryCandles(token, timeFrame, new Range<DateTime>(from, to), true, ref timeInfo);

            _candleTokens[token] = timeInfo;

            return token;
        }
        /// <summary>
        /// Отменить получение исторических свечек, зарегистрированные через
        /// <see cref="RegisterCandles(StockSharp.BusinessEntities.Security,StockSharp.AlfaDirect.AlfaTimeFrames)"/>.
        /// </summary>
        /// <param name="token">Идентификатор группировки свечек.</param>
        public void UnRegisterCandles(CandleToken token)
        {
            if (token == null)
                throw new ArgumentNullException("token");

            _candleTokens.Remove(token);
        }
		/// <summary>
		/// Получить исторические тайм-фрейм свечки из локальной базы AlfaDirect.
		/// </summary>
		/// <param name="token">Токен, для которого необходимо начать получать исторические свечки.</param>
		/// <param name="timeFrame">Тайм-фрейм.</param>
		/// <param name="range">Диапазон времени, для которого нужно получить свечки.</param>
		/// <param name="isHistory">True для исторических свечек, false для реал-тайм.</param>
		/// <param name="timeInfo"></param>
		/// <returns>Идентификатор получения исторических свечек.</returns>
		private void ProcessHistoryCandles(CandleToken token, AlfaTimeFrames timeFrame, Range<DateTime> range, bool isHistory, ref CandleTimeInfo timeInfo)
        {
			this.AddInfoLog("ProcessHistoryCandles: {0} - {1}, hist: {2}", range.Min, range.Max, isHistory);

            var candles = (List<Candle>)GetLocalHistoryData(token.Security, timeFrame, range);

			this.AddInfoLog("Received {0} candles", candles.Count);

			if (candles.Count == 0)
            {
				if(isHistory)
				{
					this.AddErrorLog("Ошибка загрузки исторических свечек. Проверьте их наличие в терминале!");
				}

            	return;
            }

			var tfCandles = (from TimeFrameCandle tfCandle in candles select tfCandle).ToArray();

			if(isHistory)
			{
				foreach (var candle in tfCandles)
				{
					// Свеча незакончена, в процессе формирования, используем для CandleStarted.
					if (candle.Time == range.Max)
					{
						CandlesStarted.SafeInvoke(token, new[] { candle });
						
						timeInfo.TimeStarted = candle.Time;
						timeInfo.IsStartedSent = true;
					}
					else
					{
						CandlesStarted.SafeInvoke(token, new[] { candle });
						CandlesFinished.SafeInvoke(token, new[] { candle });
					}
				}				
			}
			else
			{
				var lastCandle = tfCandles.Last();

				foreach (var candle in tfCandles)
				{
					if(candle != lastCandle)
					{
						CandlesFinished.SafeInvoke(token, new[] { candle });
						timeInfo.IsStartedSent = false;
					}
					else
					{
						if(! timeInfo.IsStartedSent)
						{
							CandlesStarted.SafeInvoke(token, new[] { candle });
							timeInfo.TimeStarted = candle.Time;
							timeInfo.IsStartedSent = true;

						}
						else
						{
							CandlesChanged.SafeInvoke(token, new [] {candle});
						}
					}
			}
		}
        }


	}
}